// Free Disassembler and Assembler -- Assembler
//
// Copyright (C) 2001 Oleh Yuschuk
//
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 2 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program; if not, write to the Free Software
//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

// 16.01.2002 - corrected error in processing of immediate constants.


#define STRICT

#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
//#include <dir.h>
#include <math.h>
#include <float.h>
#pragma hdrstop

#include "disasm.h"

////////////////////////////////////////////////////////////////////////////////
///////////////////////////// ASSEMBLER FUNCTIONS //////////////////////////////

// Scanner modes.
#define SA_NAME        0x0001          // Don't try to decode labels
#define SA_IMPORT      0x0002          // Allow import pseudolabel

// Types of input tokens reported by scanner.
#define SCAN_EOL       0               // End of line
#define SCAN_REG8      1               // 8-bit register
#define SCAN_REG16     2               // 16-bit register
#define SCAN_REG32     3               // 32-bit register
#define SCAN_SEG       4               // Segment register
#define SCAN_FPU       5               // FPU register
#define SCAN_MMX       6               // MMX register
#define SCAN_CR        7               // Control register
#define SCAN_DR        8               // Debug register
#define SCAN_OPSIZE    9               // Operand size modifier
#define SCAN_JMPSIZE   10              // Jump size modifier
#define SCAN_LOCAL     11              // Address on stack in form LOCAL.decimal
#define SCAN_ARG       12              // Address on stack in form ARG.decimal
#define SCAN_PTR       20              // PTR in MASM addressing statements
#define SCAN_REP       21              // REP prefix
#define SCAN_REPE      22              // REPE prefix
#define SCAN_REPNE     23              // REPNE prefix
#define SCAN_LOCK      24              // LOCK prefix
#define SCAN_NAME      25              // Command or label
#define SCAN_ICONST    26              // Hexadecimal constant
#define SCAN_DCONST    27              // Decimal constant
#define SCAN_OFS       28              // Undefined constant
#define SCAN_FCONST    29              // Floating-point constant
#define SCAN_EIP       30              // Register EIP
#define SCAN_SIGNED    31              // Keyword "SIGNED" (in expressions)
#define SCAN_UNSIGNED  32              // Keyword "UNSIGNED" (in expressions)
#define SCAN_CHAR      33              // Keyword "CHAR" (in expressions)
#define SCAN_FLOAT     34              // Keyword "FLOAT" (in expressions)
#define SCAN_DOUBLE    35              // Keyword "DOUBLE" (in expressions)
#define SCAN_FLOAT10   36              // Keyword "FLOAT10" (in expressions)
#define SCAN_STRING    37              // Keyword "STRING" (in expressions)
#define SCAN_UNICODE   38              // Keyword "UNICODE" (in expressions)
#define SCAN_MSG       39              // Pseudovariable MSG (in expressions)

#define SCAN_SYMB      64              // Any other character
#define SCAN_IMPORT    65              // Import pseudolabel
#define SCAN_ERR       255             // Definitely bad item

// Definition used by Assembler to report command matching errors.
#define MA_JMP         0x0001          // Invalid jump size modifier
#define MA_NOP         0x0002          // Wrong number of operands
#define MA_TYP         0x0004          // Bad operand type
#define MA_NOS         0x0008          // Explicit operand size expected
#define MA_SIZ         0x0010          // Bad operand size
#define MA_DIF         0x0020          // Different operand sizes
#define MA_SEG         0x0040          // Invalid segment register
#define MA_RNG         0x0080          // Constant out of expected range

typedef struct t_asmoperand {
    int            type;                 // Operand type, see beginning of file
    int            size;                 // Operand size or 0 if yet unknown
    int            index;                // Index or other register
    int            scale;                // Scale
    int            base;                 // Base register if present
    long           offset;               // Immediate value or offset
    int            anyoffset;            // Offset is present but undefined
    int            segment;              // Segment in address if present
    int            jmpmode;              // Specified jump size
} t_asmoperand;

static char* asmcmd;              // Pointer to 0-terminated source line
static int       scan;                 // Type of last scanned element
static int       prio;                 // Priority of operation (0: highest)
static char      sdata[TEXTLEN];       // Last scanned name (depends on type)
static long      idata;                // Last scanned value
static long      double fdata;         // Floating-point number
static char* asmerror;            // Explanation of last error, or NULL

// Simple and slightly recursive scanner shared by Assemble(). The scanner is
// straightforward and ineffective, but high speed is not a must here. As
// input, it uses global pointer to source line asmcmd. On exit, it fills in
// global variables scan, prio, sdata, idata and/or fdata. If some error is
// detected, asmerror points to error message, otherwise asmerror remains
// unchanged.
static void Scanasm(int mode) {
    int i, j, base, maxdigit;
    long decimal, hex;
    long double floating, divisor;
    char s[TEXTLEN], * pcmd;
    sdata[0] = '\0';
    idata = 0;
    if (asmcmd == NULL) {
        asmerror = "NULL input line"; scan = SCAN_ERR; return;
    };
    while (*asmcmd == ' ' || *asmcmd == '\t')
        asmcmd++;                          // Skip leading spaces
    if (*asmcmd == '\0' || *asmcmd == ';') {
        scan = SCAN_EOL; return;
    };          // Empty line
    if (isalpha(*asmcmd) || *asmcmd == '_' || *asmcmd == '@') {
        sdata[0] = *asmcmd++; i = 1;           // Some keyword or identifier
        while ((isalnum(*asmcmd) || *asmcmd == '_' || *asmcmd == '@') &&
            i < sizeof(sdata))
            sdata[i++] = *asmcmd++;
        if (i >= sizeof(sdata)) {
            asmerror = "Too long identifier"; scan = SCAN_ERR; return;
        };
        sdata[i] = '\0';
        while (*asmcmd == ' ' || *asmcmd == '\t')
            asmcmd++;                        // Skip trailing spaces
        strcpy(s, sdata); strupr(s);
        for (j = 0; j <= 8; j++) {             // j==8 means "any register"
            if (strcmp(s, regname[0][j]) != 0) continue;
            idata = j; scan = SCAN_REG8;         // 8-bit register
            return;
        };
        for (j = 0; j <= 8; j++) {
            if (strcmp(s, regname[1][j]) != 0) continue;
            idata = j; scan = SCAN_REG16;        // 16-bit register
            return;
        };
        for (j = 0; j <= 8; j++) {
            if (strcmp(s, regname[2][j]) != 0) continue;
            idata = j; scan = SCAN_REG32;        // 32-bit register
            return;
        };
        for (j = 0; j < 6; j++) {
            if (strcmp(s, segname[j]) != 0) continue;
            idata = j; scan = SCAN_SEG;          // Segment register
            while (*asmcmd == ' ' || *asmcmd == '\t')
                asmcmd++;                      // Skip trailing spaces
            return;
        };
        if (strcmp(s, "ST") == 0) {
            pcmd = asmcmd; Scanasm(SA_NAME);   // FPU register
            if (scan != SCAN_SYMB || idata != '(') {
                asmcmd = pcmd;                   // Undo last scan
                idata = 0; scan = SCAN_FPU; return;
            };
            Scanasm(SA_NAME); j = idata;
            if ((scan != SCAN_ICONST && scan != SCAN_DCONST) || idata < 0 || idata>7) {
                asmerror = "FPU registers have indexes 0 to 7";
                scan = SCAN_ERR; return;
            };
            Scanasm(SA_NAME);
            if (scan != SCAN_SYMB || idata != ')') {
                asmerror = "Closing parenthesis expected";
                scan = SCAN_ERR; return;
            };
            idata = j; scan = SCAN_FPU; return;
        };
        for (j = 0; j <= 8; j++) {
            if (strcmp(s, fpuname[j]) != 0) continue;
            idata = j; scan = SCAN_FPU;          // FPU register (alternative coding)
            return;
        };
        for (j = 0; j <= 8; j++) {
            if (strcmp(s, mmxname[j]) != 0) continue;
            idata = j; scan = SCAN_MMX;          // MMX register
            return;
        };
        for (j = 0; j <= 8; j++) {
            if (strcmp(s, crname[j]) != 0) continue;
            idata = j; scan = SCAN_CR;           // Control register
            return;
        };
        for (j = 0; j <= 8; j++) {
            if (strcmp(s, drname[j]) != 0) continue;
            idata = j; scan = SCAN_DR;           // Debug register
            return;
        };
        for (j = 0; j < sizeof(sizename) / sizeof(sizename[0]); j++) {
            if (strcmp(s, sizename[j]) != 0) continue;
            pcmd = asmcmd; Scanasm(SA_NAME);
            if (scan != SCAN_PTR)              // Fetch non-functional "PTR"
                asmcmd = pcmd;
            idata = j; scan = SCAN_OPSIZE;       // Operand (data) size in bytes
            return;
        };
        if (strcmp(s, "EIP") == 0) {          // Register EIP
            scan = SCAN_EIP; idata = 0; return;
        };
        if (strcmp(s, "SHORT") == 0) {        // Relative jump has 1-byte offset
            scan = SCAN_JMPSIZE; idata = 1; return;
        };
        if (strcmp(s, "LONG") == 0) {         // Relative jump has 4-byte offset
            scan = SCAN_JMPSIZE; idata = 2; return;
        };
        if (strcmp(s, "NEAR") == 0) {         // Jump within same code segment
            scan = SCAN_JMPSIZE; idata = 4; return;
        };
        if (strcmp(s, "FAR") == 0) {          // Jump to different code segment
            scan = SCAN_JMPSIZE; idata = 8; return;
        };
        if (strcmp(s, "LOCAL") == 0 && *asmcmd == '.') {
            asmcmd++;
            while (*asmcmd == ' ' || *asmcmd == '\t')
                asmcmd++;                      // Skip trailing spaces
            if (!isdigit(*asmcmd)) {
                asmerror = "Integer number expected";
                scan = SCAN_ERR; return;
            };
            while (isdigit(*asmcmd))         // LOCAL index is decimal number!
                idata = idata * 10 + (*asmcmd++) - '0';
            scan = SCAN_LOCAL; return;
        };
        if (strcmp(s, "ARG") == 0 && *asmcmd == '.') {
            asmcmd++;
            while (*asmcmd == ' ' || *asmcmd == '\t')
                asmcmd++;                      // Skip trailing spaces
            if (!isdigit(*asmcmd)) {
                asmerror = "Integer number expected";
                scan = SCAN_ERR; return;
            };
            while (isdigit(*asmcmd))         // ARG index is decimal number!
                idata = idata * 10 + (*asmcmd++) - '0';
            scan = SCAN_ARG; return;
        };
        if (strcmp(s, "REP") == 0) {
            scan = SCAN_REP; return;
        };        // REP prefix
        if (strcmp(s, "REPE") == 0 || strcmp(s, "REPZ") == 0) {
            scan = SCAN_REPE; return;
        };       // REPE prefix
        if (strcmp(s, "REPNE") == 0 || strcmp(s, "REPNZ") == 0) {
            scan = SCAN_REPNE; return;
        };      // REPNE prefix
        if (strcmp(s, "LOCK") == 0) {
            scan = SCAN_LOCK; return;
        };       // LOCK prefix
        if (strcmp(s, "PTR") == 0) {
            scan = SCAN_PTR; return;
        };        // PTR in MASM addressing statements
        if (strcmp(s, "CONST") == 0 || strcmp(s, "OFFSET") == 0) {
            scan = SCAN_OFS; return;
        };        // Present but undefined offset/constant
        if (strcmp(s, "SIGNED") == 0) {
            scan = SCAN_SIGNED; return;
        };     // Keyword "SIGNED" (in expressions)
        if (strcmp(s, "UNSIGNED") == 0) {
            scan = SCAN_UNSIGNED; return;
        };   // Keyword "UNSIGNED" (in expressions)
        if (strcmp(s, "CHAR") == 0) {
            scan = SCAN_CHAR; return;
        };       // Keyword "CHAR" (in expressions)
        if (strcmp(s, "FLOAT") == 0) {
            scan = SCAN_FLOAT; return;
        };      // Keyword "FLOAT" (in expressions)
        if (strcmp(s, "DOUBLE") == 0) {
            scan = SCAN_DOUBLE; return;
        };     // Keyword "DOUBLE" (in expressions)
        if (strcmp(s, "FLOAT10") == 0) {
            scan = SCAN_FLOAT10; return;
        };    // Keyword "FLOAT10" (in expressions)
        if (strcmp(s, "STRING") == 0) {
            scan = SCAN_STRING; return;
        };     // Keyword "STRING" (in expressions)
        if (strcmp(s, "UNICODE") == 0) {
            scan = SCAN_UNICODE; return;
        };    // Keyword "UNICODE" (in expressions)
        if (strcmp(s, "MSG") == 0) {
            scan = SCAN_MSG; return;
        };        // Pseudovariable MSG (in expressions)
        if (mode & SA_NAME) {
            idata = i; scan = SCAN_NAME;         // Don't try to decode symbolic label
            return;
        }
        asmerror = "Unknown identifier";
        scan = SCAN_ERR; return;
    }
    else if (isdigit(*asmcmd)) {         // Constant
        base = 0; maxdigit = 0; decimal = hex = 0L; floating = 0.0;
        if (asmcmd[0] == '0' && toupper(asmcmd[1]) == 'X') {
            base = 16; asmcmd += 2;
        };           // Force hexadecimal number
        while (1) {
            if (isdigit(*asmcmd)) {
                decimal = decimal * 10 + (*asmcmd) - '0';
                floating = floating * 10.0 + (*asmcmd) - '0';
                hex = hex * 16 + (*asmcmd) - '0';
                if (maxdigit == 0) maxdigit = 9;
                asmcmd++;
            }
            else if (isxdigit(*asmcmd)) {
                hex = hex * 16 + toupper(*asmcmd++) - 'A' + 10;
                maxdigit = 15;
            }
            else break;
        };
        if (maxdigit == 0) {
            asmerror = "Hexadecimal digits after 0x... expected";
            scan = SCAN_ERR; return;
        };
        if (toupper(*asmcmd) == 'H') {       // Force hexadecimal number
            if (base == 16) {
                asmerror = "Please don't mix 0xXXXX and XXXXh forms";
                scan = SCAN_ERR; return;
            };
            asmcmd++;
            idata = hex; scan = SCAN_ICONST;
            while (*asmcmd == ' ' || *asmcmd == '\t') asmcmd++;
            return;
        };
        if (*asmcmd == '.') {                // Force decimal number
            if (base == 16 || maxdigit > 9) {
                asmerror = "Not a decimal number"; scan = SCAN_ERR; return;
            };
            asmcmd++;
            if (isdigit(*asmcmd) || toupper(*asmcmd) == 'E') {
                divisor = 1.0;
                while (isdigit(*asmcmd)) {     // Floating-point number
                    divisor /= 10.0;
                    floating += divisor * (*asmcmd - '0');
                    asmcmd++;
                };
                if (toupper(*asmcmd) == 'E') {
                    asmcmd++;
                    if (*asmcmd == '-') { base = -1; asmcmd++; }
                    else base = 1;
                    if (!isdigit(*asmcmd)) {
                        asmerror = "Invalid exponent"; scan = SCAN_ERR; return;
                    };
                    decimal = 0;
                    while (isdigit(*asmcmd)) {
                        if (decimal < 65536L) decimal = decimal * 10 + (*asmcmd++) - '0';
                    };
                    floating *= pow(10.0, decimal * base);
                };
                fdata = floating;
                scan = SCAN_FCONST; return;
            }
            else {
                idata = decimal; scan = SCAN_DCONST;
                while (*asmcmd == ' ' || *asmcmd == '\t') asmcmd++;
                return;
            };
        };
        idata = hex; scan = SCAN_ICONST;       // Default is hexadecimal
        while (*asmcmd == ' ' || *asmcmd == '\t') asmcmd++;
        return;
    }
    else if (*asmcmd == '\'') {            // Character constant
        asmcmd++;
        if (*asmcmd == '\0' || (*asmcmd == '\\' && asmcmd[1] == '\0')) {
            asmerror = "Unterminated character constant"; scan = SCAN_ERR; return;
        };
        if (*asmcmd == '\'') {
            asmerror = "Empty character constant"; scan = SCAN_ERR; return;
        };
        if (*asmcmd == '\\') asmcmd++;
        idata = *asmcmd++;
        if (*asmcmd != '\'') {
            asmerror = "Unterminated character constant"; scan = SCAN_ERR; return;
        };
        asmcmd++;
        while (*asmcmd == ' ' || *asmcmd == '\t') asmcmd++;
        scan = SCAN_ICONST; return;
    }
    else {                               // Any other character or combination
        idata = sdata[0] = *asmcmd++; sdata[1] = sdata[2] = '\0';
        if (idata == '|' && *asmcmd == '|') {
            idata = '||'; prio = 10;             // '||'
            sdata[1] = *asmcmd++;
        }
        else if (idata == '&' && *asmcmd == '&') {
            idata = '&&'; prio = 9;              // '&&'
            sdata[1] = *asmcmd++;
        }
        else if (idata == '=' && *asmcmd == '=') {
            idata = '=='; prio = 5;              // '=='
            sdata[1] = *asmcmd++;
        }
        else if (idata == '!' && *asmcmd == '=') {
            idata = '!='; prio = 5;              // '!='
            sdata[1] = *asmcmd++;
        }
        else if (idata == '<' && *asmcmd == '=') {
            idata = '<='; prio = 4;              // '<='
            sdata[1] = *asmcmd++;
        }
        else if (idata == '>' && *asmcmd == '=') {
            idata = '>='; prio = 4;              // '>='
            sdata[1] = *asmcmd++;
        }
        else if (idata == '<' && *asmcmd == '<') {
            idata = '<<'; prio = 3;              // '<<'
            sdata[1] = *asmcmd++;
        }
        else if (idata == '>' && *asmcmd == '>') {
            idata = '>>'; prio = 3;              // '>>'
            sdata[1] = *asmcmd++;
        }
        else if (idata == '|') prio = 8;       // '|'
        else if (idata == '^') prio = 7;       // '^'
        else if (idata == '&') prio = 6;       // '&'
        else if (idata == '<') {
            if (*asmcmd == '&') {              // Import pseudolabel (for internal use)
                if ((mode & SA_IMPORT) == 0) {
                    asmerror = "Syntax error"; scan = SCAN_ERR; return;
                };
                asmcmd++; i = 0;
                while (*asmcmd != '\0' && *asmcmd != '>') {
                    sdata[i++] = *asmcmd++;
                    if (i >= sizeof(sdata)) {
                        asmerror = "Too long import name"; scan = SCAN_ERR; return;
                    };
                };
                if (*asmcmd != '>') {
                    asmerror = "Unterminated import name"; scan = SCAN_ERR; return;
                };
                asmcmd++; sdata[i] = '\0';
                scan = SCAN_IMPORT; return;
            }
            else prio = 4;
        }                   // '<'
        else if (idata == '>') prio = 4;       // '>'
        else if (idata == '+') prio = 2;       // '+'
        else if (idata == '-') prio = 2;       // '-'
        else if (idata == '*') prio = 1;       // '*'
        else if (idata == '/') prio = 1;       // '/'
        else if (idata == '%') prio = 1;       // '%'
        else if (idata == ']') {
            pcmd = asmcmd; Scanasm(SA_NAME);
            if (scan != SCAN_SYMB || idata != '[') {
                idata = ']'; asmcmd = pcmd; prio = 0;
            }
            else {
                idata = '+'; prio = 2;             // Translate '][' to '+'
            };
        }
        else prio = 0;                       // Any other character
        scan = SCAN_SYMB;
        return;
    };
};

// Fetches one complete operand from the input line and fills in structure op
// with operand's data. Expects that first token of the operand is already
// scanned. Supports operands in generalized form (for example, R32 means any
// of general-purpose 32-bit integer registers).
static void Parseasmoperand(t_asmoperand* op) {
    int i, j, bracket, sign, xlataddr;
    int reg, r[9];
    long offset;
    if (scan == SCAN_EOL || scan == SCAN_ERR)
        return;                            // No or bad operand
    // Jump or call address may begin with address size modifier(s) SHORT, LONG,
    // NEAR and/or FAR. Not all combinations are allowed. After operand is
    // completely parsed, this function roughly checks whether modifier is
    // allowed. Exact check is done in Assemble().
    if (scan == SCAN_JMPSIZE) {
        j = 0;
        while (scan == SCAN_JMPSIZE) {
            j |= idata;                        // Fetch all size modifiers
            Scanasm(0);
        };
        if (
            ((j & 0x03) == 0x03) ||            // Mixed SHORT and LONG
            ((j & 0x0C) == 0x0C) ||            // Mixed NEAR and FAR
            ((j & 0x09) == 0x09)               // Mixed FAR and SHORT
            ) {
            asmerror = "Invalid combination of jump address modifiers";
            scan = SCAN_ERR; return;
        };
        if ((j & 0x08) == 0) j |= 0x04;        // Force NEAR if not FAR
        op->jmpmode = j;
    };
    // Simple operands are either register or constant, their processing is
    // obvious and straightforward.
    if (scan == SCAN_REG8 || scan == SCAN_REG16 || scan == SCAN_REG32) {
        op->type = REG; op->index = idata;     // Integer general-purpose register
        if (scan == SCAN_REG8) op->size = 1;
        else if (scan == SCAN_REG16) op->size = 2;
        else op->size = 4;
    }
    else if (scan == SCAN_FPU) {           // FPU register
        op->type = RST; op->index = idata;
    }
    else if (scan == SCAN_MMX) {           // MMX or 3DNow! register
        op->type = RMX; op->index = idata;
    }
    else if (scan == SCAN_CR) {            // Control register
        op->type = CRX; op->index = idata;
    }
    else if (scan == SCAN_DR) {            // Debug register
        op->type = DRX; op->index = idata;
    }
    else if (scan == SCAN_SYMB && idata == '-') {
        Scanasm(0);                        // Negative constant
        if (scan != SCAN_ICONST && scan != SCAN_DCONST && scan != SCAN_OFS) {
            asmerror = "Integer number expected";
            scan = SCAN_ERR; return;
        };
        op->type = IMM; op->offset = -idata;
        if (scan == SCAN_OFS) op->anyoffset = 1;
    }
    else if (scan == SCAN_SYMB && idata == '+') {
        Scanasm(0);                        // Positive constant
        if (scan != SCAN_ICONST && scan != SCAN_DCONST && scan != SCAN_OFS) {
            asmerror = "Integer number expected";
            scan = SCAN_ERR; return;
        };
        op->type = IMM; op->offset = idata;
        if (scan == SCAN_OFS) op->anyoffset = 1;
    }
    else if (scan == SCAN_ICONST || scan == SCAN_DCONST || scan == SCAN_OFS) {
        j = idata;
        if (scan == SCAN_OFS) op->anyoffset = 1;
        Scanasm(0);
        if (scan == SCAN_SYMB && idata == ':') {
            Scanasm(0);                      // Absolute long address (seg:offset)
            if (scan != SCAN_ICONST && scan != SCAN_DCONST && scan != SCAN_OFS) {
                asmerror = "Integer address expected";
                scan = SCAN_ERR; return;
            };
            op->type = JMF; op->offset = idata; op->segment = j;
            if (scan == SCAN_OFS) op->anyoffset = 1;
        }
        else {
            op->type = IMM; op->offset = j;      // Constant without sign
            return;                          // Next token already scanned
        };
    }
    else if (scan == SCAN_FCONST) {
        asmerror = "Floating-point numbers are not allowed in command";
        scan = SCAN_ERR; return;
    }
    // Segment register or address.
    else if (scan == SCAN_SEG || scan == SCAN_OPSIZE ||
        (scan == SCAN_SYMB && idata == '[')
        ) {                                  // Segment register or address
        bracket = 0;
        if (scan == SCAN_SEG) {
            j = idata; Scanasm(0);
            if (scan != SCAN_SYMB || idata != ':') {
                op->type = SGM; op->index = j;     // Segment register as operand
                return;
            };                     // Next token already scanned
            op->segment = j; Scanasm(0);
        };
        // Scan 32-bit address. This parser does not support 16-bit addresses.
        // First of all, get size of operand (optional), segment register (optional)
        // and opening bracket (required).
        while (1) {
            if (scan == SCAN_SYMB && idata == '[') {
                if (bracket) {                 // Bracket
                    asmerror = "Only one opening bracket allowed";
                    scan = SCAN_ERR; return;
                };
                bracket = 1;
            }
            else if (scan == SCAN_OPSIZE) {
                if (op->size != 0) {             // Size of operand
                    asmerror = "Duplicated size modifier";
                    scan = SCAN_ERR; return;
                };
                op->size = idata;
            }
            else if (scan == SCAN_SEG) {
                if (op->segment != SEG_UNDEF) {  // Segment register
                    asmerror = "Duplicated segment register";
                    scan = SCAN_ERR; return;
                };
                op->segment = idata; Scanasm(0);
                if (scan != SCAN_SYMB || idata != ':') {
                    asmerror = "Semicolon expected";
                    scan = SCAN_ERR; return;
                };
            }
            else if (scan == SCAN_ERR)
                return;
            else break;                      // None of expected address elements
            Scanasm(0);
        };
        if (bracket == 0) {
            asmerror = "Address expression requires brackets";
            scan = SCAN_ERR; return;
        };
        // Assembling a 32-bit address may be a kind of nigthmare, due to a large
        // number of allowed forms. Parser collects immediate offset in op->offset
        // and count for each register in array r[]. Then it decides whether this
        // combination is valid and determines scale, index and base. Assemble()
        // will use these numbers to select address form (with or without SIB byte,
        // 8- or 32-bit offset, use segment prefix or not). As a useful side effect
        // of this technique, one may specify, for example, [EAX*5] which will
        // correctly assemble to [EAX*4+EAX].
        for (i = 0; i <= 8; i++) r[i] = 0;
        sign = '+';                          // Default sign for the first operand
        xlataddr = 0;
        while (1) {                        // Get SIB and offset
            if (scan == SCAN_SYMB && (idata == '+' || idata == '-')) {
                sign = idata; Scanasm(0);
            };
            if (scan == SCAN_ERR) return;
            if (sign == '?') {
                asmerror = "Syntax error"; scan = SCAN_ERR; return;
            };
            // Register AL appears as part of operand of (seldom used) command XLAT.
            if (scan == SCAN_REG8 && idata == REG_EAX) {
                if (sign == '-') {
                    asmerror = "Unable to subtract register"; scan = SCAN_ERR; return;
                };
                if (xlataddr != 0) {
                    asmerror = "Too many registers"; scan = SCAN_ERR; return;
                };
                xlataddr = 1;
                Scanasm(0);
            }
            else if (scan == SCAN_REG16) {
                asmerror = "Sorry, 16-bit addressing is not supported";
                scan = SCAN_ERR; return;
            }
            else if (scan == SCAN_REG32) {
                if (sign == '-') {
                    asmerror = "Unable to subtract register"; scan = SCAN_ERR; return;
                };
                reg = idata; Scanasm(0);
                if (scan == SCAN_SYMB && idata == '*') {
                    Scanasm(0);                  // Try index*scale
                    if (scan == SCAN_ERR) return;
                    if (scan == SCAN_OFS) {
                        asmerror = "Undefined scale is not allowed"; scan = SCAN_ERR; return;
                    };
                    if (scan != SCAN_ICONST && scan != SCAN_DCONST) {
                        asmerror = "Syntax error"; scan = SCAN_ERR; return;
                    };
                    if (idata == 6 || idata == 7 || idata > 9) {
                        asmerror = "Invalid scale"; scan = SCAN_ERR; return;
                    };
                    r[reg] += idata;
                    Scanasm(0);
                }
                else r[reg]++;
            }               // Simple register
            else if (scan == SCAN_LOCAL) {
                r[REG_EBP]++;
                op->offset -= idata * 4;
                Scanasm(0);
            }
            else if (scan == SCAN_ARG) {
                r[REG_EBP]++;
                op->offset += (idata + 1) * 4;
                Scanasm(0);
            }
            else if (scan == SCAN_ICONST || scan == SCAN_DCONST) {
                offset = idata; Scanasm(0);
                if (scan == SCAN_SYMB && idata == '*') {
                    Scanasm(0);                  // Try scale*index
                    if (scan == SCAN_ERR) return;
                    if (sign == '-') {
                        asmerror = "Unable to subtract register"; scan = SCAN_ERR; return;
                    };
                    if (scan == SCAN_REG16) {
                        asmerror = "Sorry, 16-bit addressing is not supported";
                        scan = SCAN_ERR; return;
                    };
                    if (scan != SCAN_REG32) {
                        asmerror = "Syntax error"; scan = SCAN_ERR; return;
                    };
                    if (offset == 6 || offset == 7 || offset > 9) {
                        asmerror = "Invalid scale"; scan = SCAN_ERR; return;
                    };
                    r[idata] += offset;
                    Scanasm(0);
                }
                else {
                    if (sign == '-') op->offset -= offset;
                    else op->offset += offset;
                };
            }
            else if (scan == SCAN_OFS) {
                Scanasm(0);
                if (scan == SCAN_SYMB && idata == '*') {
                    asmerror = "Undefined scale is not allowed"; scan = SCAN_ERR; return;
                }
                else {
                    op->anyoffset = 1;
                };
            }
            else break;                      // None of expected address elements
            if (scan == SCAN_SYMB && idata == ']') break;
            sign = '?';
        };
        if (scan == SCAN_ERR) return;
        if (scan != SCAN_SYMB || idata != ']') {
            asmerror = "Syntax error";
            scan = SCAN_ERR; return;
        };
        // Process XLAT address separately.
        if (xlataddr != 0) {                 // XLAT address in form [EBX+AX]
            for (i = 0; i <= 8; i++) {           // Check which registers used
                if (i == REG_EBX) continue;
                if (r[i] != 0) break;
            };
            if (i <= 8 || r[REG_EBX] != 1 || op->offset != 0 || op->anyoffset != 0) {
                asmerror = "Invalid address"; scan = SCAN_ERR; return;
            };
            op->type = MXL;
        }
        // Determine scale, index and base.
        else {
            j = 0;                             // Number of used registers
            for (i = 0; i <= 8; i++) {
                if (r[i] == 0)
                    continue;                    // Unused register
                if (r[i] == 3 || r[i] == 5 || r[i] == 9) {
                    if (op->index >= 0 || op->base >= 0) {
                        if (j == 0) asmerror = "Invalid scale";
                        else asmerror = "Too many registers";
                        scan = SCAN_ERR; return;
                    };
                    op->index = op->base = i;
                    op->scale = r[i] - 1;
                }
                else if (r[i] == 2 || r[i] == 4 || r[i] == 8) {
                    if (op->index >= 0) {
                        if (j <= 1) asmerror = "Only one register may be scaled";
                        else asmerror = "Too many registers";
                        scan = SCAN_ERR; return;
                    };
                    op->index = i; op->scale = r[i];
                }
                else if (r[i] == 1) {
                    if (op->base < 0)
                        op->base = i;
                    else if (op->index < 0) {
                        op->index = i; op->scale = 1;
                    }
                    else {
                        asmerror = "Too many registers";
                        scan = SCAN_ERR; return;
                    };
                }
                else {
                    asmerror = "Invalid scale"; scan = SCAN_ERR; return;
                };
                j++;
            };
            op->type = MRG;
        };
    }
    else {
        asmerror = "Unrecognized operand"; scan = SCAN_ERR; return;
    };
    // In general, address modifier is allowed only with address expression which
    // is a constant, a far address or a memory expression. More precise check
    // will be done later in Assemble().
    if (op->jmpmode != 0 && op->type != IMM && op->type != JMF && op->type != MRG) {
        asmerror = "Jump address modifier is not allowed";
        scan = SCAN_ERR; return;
    };
    Scanasm(0);                          // Fetch next token from input line
};

// Function assembles text into 32-bit 80x86 machine code. It supports imprecise
// operands (for example, R32 stays for any general-purpose 32-bit register).
// This allows to search for incomplete commands. Command is precise when all
// significant bytes in model.mask are 0xFF. Some commands have more than one
// decoding. By calling Assemble() with attempt=0,1... and constsize=0,1,2,3 one
// gets also alternative variants (bit 0x1 of constsize is responsible for size
// of address constant and bit 0x2 - for immediate data). However, only one
// address form is generated ([EAX*2], but not [EAX+EAX]; [EBX+EAX] but not
// [EAX+EBX]; [EAX] will not use SIB byte; no DS: prefix and so on). Returns
// number of bytes in assembled code or non-positive number in case of detected
// error. This number is the negation of the offset in the input text where the
// error encountered. Unfortunately, BC 4.52 is unable to compile the switch
// (arg) in this code when any common subexpression optimization is on. The
// next #pragma statement disables all optimizations.

//#pragma option -Od                     // No optimizations, or BC 4.52 crashes

int Assemble(char* cmd, ulong ip, t_asmmodel* model, int attempt,
    int constsize, char* errtext) {
    int i, j, k, namelen, nameok, arg, match, datasize, addrsize, bytesize, minop, maxop;
    int rep, lock, segment, jmpsize, jmpmode, longjump;
    int hasrm, hassib, dispsize, immsize;
    int anydisp, anyimm, anyjmp;
    long l, displacement, immediate, jmpoffset;
    // WARN: suppress warning
    jmpoffset = 0;
    char name[32], * nameend;
    char tcode[MAXCMDSIZE], tmask[MAXCMDSIZE];
    t_asmoperand aop[3], * op;             // Up to 3 operands allowed
    const t_cmddata* pd;
    if (model != NULL) model->length = 0;
    if (cmd == NULL || model == NULL || errtext == NULL) {
        if (errtext != NULL) strcpy(errtext, "Internal OLLYDBG error");
        return 0;
    };                       // Error in parameters
    asmcmd = cmd;
    rep = lock = 0; errtext[0] = '\0';
    Scanasm(SA_NAME);
    if (scan == SCAN_EOL)                  // End of line, nothing to assemble
        return 0;
    while (1) {                          // Fetch all REPxx and LOCK prefixes
        if (scan == SCAN_REP || scan == SCAN_REPE || scan == SCAN_REPNE) {
            if (rep != 0) {
                strcpy(errtext, "Duplicated REP prefix"); goto error;
            };
            rep = scan;
        }
        else if (scan == SCAN_LOCK) {
            if (lock != 0) {
                strcpy(errtext, "Duplicated LOCK prefix"); goto error;
            };
            lock = scan;
        }
        else break;                        // No more prefixes
        Scanasm(SA_NAME);
    };
    if (scan != SCAN_NAME || idata > 16) {
        strcpy(errtext, "Command mnemonic expected"); goto error;
    };
    nameend = asmcmd;
    strupr(sdata);
    // Prepare full mnemonic (including repeat prefix, if any).
    if (rep == SCAN_REP) sprintf(name, "REP %s", sdata);
    else if (rep == SCAN_REPE) sprintf(name, "REPE %s", sdata);
    else if (rep == SCAN_REPNE) sprintf(name, "REPNE %s", sdata);
    else strcpy(name, sdata);
    Scanasm(0);
    // Parse command operands (up to 3). Note: jump address is always the first
    // (and only) operand in actual command set.
    for (i = 0; i < 3; i++) {
        aop[i].type = NNN;                   // No operand
        aop[i].size = 0;                     // Undefined size
        aop[i].index = -1;                   // No index
        aop[i].scale = 0;                    // No scale
        aop[i].base = -1;                    // No base
        aop[i].offset = 0;                   // No offset
        aop[i].anyoffset = 0;                // No offset
        aop[i].segment = SEG_UNDEF;          // No segment
        aop[i].jmpmode = 0;
    };               // No jump size modifier
    Parseasmoperand(aop + 0);
    jmpmode = aop[0].jmpmode;
    if (jmpmode != 0) jmpmode |= 0x80;
    if (scan == SCAN_SYMB && idata == ',') {
        Scanasm(0);
        Parseasmoperand(aop + 1);
        if (scan == SCAN_SYMB && idata == ',') {
            Scanasm(0);
            Parseasmoperand(aop + 2);
        };
    };
    if (scan == SCAN_ERR) {
        strcpy(errtext, asmerror); goto error;
    };
    if (scan != SCAN_EOL) {
        strcpy(errtext, "Extra input after operand"); goto error;
    };
    // If jump size is not specified, function tries to use short jump. If
    // attempt fails, it retries with long form.
    longjump = 0;                          // Try short jump on the first pass
retrylongjump:
    nameok = 0;
    // Some commands allow different number of operands. Variables minop and
    // maxop accumulate their minimal and maximal counts. The numbers are not
    // used in assembly process but allow for better error diagnostics.
    minop = 3; maxop = 0;
    // Main assembly loop: try to find the command which matches all operands,
    // but do not process operands yet.
    namelen = strlen(name);
    for (pd = cmddata; pd->mask != 0; pd++) {
        if (pd->name[0] == '&') {            // Mnemonic depends on operand size
            j = 1;
            datasize = 2;
            addrsize = 4;
            while (1) {                      // Try all mnemonics (separated by ':')
                for (i = 0; pd->name[j] != '\0' && pd->name[j] != ':'; j++) {
                    if (pd->name[j] == '*') {
                        if (name[i] == 'W') { datasize = 2; i++; }
                        else if (name[i] == 'D') { datasize = 4; i++; }
                        else if (sizesens == 0) datasize = 2;
                        else datasize = 4;
                    }
                    else if (pd->name[j] == name[i]) i++;
                    else break;
                };
                if (name[i] == '\0' && (pd->name[j] == '\0' || pd->name[j] == ':'))
                    break;                       // Bingo!
                while (pd->name[j] != '\0' && pd->name[j] != ':')
                    j++;
                if (pd->name[j] == ':') {
                    j++; datasize = 4;
                }           // Retry with 32-bit mnenonic
                else {
                    i = 0; break;                  // Comparison failed
                };
            };
            if (i == 0) continue;
        }
        else if (pd->name[0] == '$') {       // Mnemonic depends on address size
            j = 1;
            datasize = 0;
            addrsize = 2;
            while (1) {                      // Try all mnemonics (separated by ':')
                for (i = 0; pd->name[j] != '\0' && pd->name[j] != ':'; j++) {
                    if (pd->name[j] == '*') {
                        if (name[i] == 'W') { addrsize = 2; i++; }
                        else if (name[i] == 'D') { addrsize = 4; i++; }
                        else if (sizesens == 0) addrsize = 2;
                        else addrsize = 4;
                    }
                    else if (pd->name[j] == name[i]) i++;
                    else break;
                };
                if (name[i] == '\0' && (pd->name[j] == '\0' || pd->name[j] == ':'))
                    break;                       // Bingo!
                while (pd->name[j] != '\0' && pd->name[j] != ':')
                    j++;
                if (pd->name[j] == ':') {
                    j++; addrsize = 4;
                }           // Retry with 32-bit mnenonic
                else {
                    i = 0; break;                  // Comparison failed
                };
            };
            if (i == 0) continue;
        }
        else {                             // Compare with all synonimes
            j = k = 0;
            datasize = 0;                      // Default settings
            addrsize = 4;
            while (1) {
                while (pd->name[j] != ',' && pd->name[j] != '\0') j++;
                if (j - k == namelen && strnicmp(name, pd->name + k, namelen) == 0) break;
                k = j + 1; if (pd->name[j] == '\0') break;
                j = k;
            };
            if (k > j) continue;
        };
        // For error diagnostics it is important to know whether mnemonic exists.
        nameok++;
        if (pd->arg1 == NNN || pd->arg1 >= PSEUDOOP)
            minop = 0;
        else if (pd->arg2 == NNN || pd->arg2 >= PSEUDOOP) {
            if (minop > 1) minop = 1;
            if (maxop < 1) maxop = 1;
        }
        else if (pd->arg3 == NNN || pd->arg3 >= PSEUDOOP) {
            if (minop > 2) minop = 2;
            if (maxop < 2) maxop = 2;
        }
        else
            maxop = 3;
        // Determine default and allowed operand size(s).
        if (pd->bits == FF) datasize = 2;      // Forced 16-bit size
        if (pd->bits == WW || pd->bits == WS || pd->bits == W3 || pd->bits == WP)
            bytesize = 1;                      // 1-byte size allowed
        else
            bytesize = 0;                      // Word/dword size only
        // Check whether command operands match specified. If so, variable match
        // remains zero, otherwise it contains kind of mismatch. This allows for
        // better error diagnostics.
        match = 0;
        for (j = 0; j < 3; j++) {              // Up to 3 operands
            op = aop + j;
            if (j == 0) arg = pd->arg1;
            else if (j == 1) arg = pd->arg2;
            else arg = pd->arg3;
            if (arg == NNN || arg >= PSEUDOOP) {
                if (op->type != NNN)             // No more arguments
                    match |= MA_NOP;
                break;
            };
            if (op->type == NNN) {
                match |= MA_NOP; break;
            };       // No corresponding operand
            switch (arg) {
            case REG:                      // Integer register in Reg field
            case RCM:                      // Integer register in command byte
            case RAC:                      // Accumulator (AL/AX/EAX, implicit)
                if (op->type != REG) match |= MA_TYP;
                if (arg == RAC && op->index != REG_EAX && op->index != 8) match |= MA_TYP;
                if (bytesize == 0 && op->size == 1) match |= MA_SIZ;
                if (datasize == 0) datasize = op->size;
                if (datasize != op->size) match |= MA_DIF;
                break;
            case RG4:                      // Integer 4-byte register in Reg field
                if (op->type != REG) match |= MA_TYP;
                if (op->size != 4) match |= MA_SIZ;
                if (datasize == 0) datasize = op->size;
                if (datasize != op->size) match |= MA_DIF;
                break;
            case RAX:                      // AX (2-byte, implicit)
                if (op->type != REG || (op->index != REG_EAX && op->index != 8))
                    match |= MA_TYP;
                if (op->size != 2) match |= MA_SIZ;
                if (datasize == 0) datasize = op->size;
                if (datasize != op->size) match |= MA_DIF;
                break;
            case RDX:                      // DX (16-bit implicit port address)
                if (op->type != REG || (op->index != REG_EDX && op->index != 8))
                    match |= MA_TYP;
                if (op->size != 2) match |= MA_SIZ; break;
            case RCL:                      // Implicit CL register (for shifts)
                if (op->type != REG || (op->index != REG_ECX && op->index != 8))
                    match |= MA_TYP;
                if (op->size != 1) match |= MA_SIZ;
                break;
            case RS0:                      // Top of FPU stack (ST(0))
                if (op->type != RST || (op->index != 0 && op->index != 8))
                    match |= MA_TYP;
                break;
            case RST:                      // FPU register (ST(i)) in command byte
                if (op->type != RST) match |= MA_TYP; break;
            case RMX:                      // MMX register MMx
            case R3D:                      // 3DNow! register MMx
                if (op->type != RMX) match |= MA_TYP; break;
            case MRG:                      // Memory/register in ModRM byte
                if (op->type != MRG && op->type != REG) match |= MA_TYP;
                if (bytesize == 0 && op->size == 1) match |= MA_SIZ;
                if (datasize == 0) datasize = op->size;
                if (op->size != 0 && op->size != datasize) match |= MA_DIF;
                break;
            case MR1:                      // 1-byte memory/register in ModRM byte
                if (op->type != MRG && op->type != REG) match |= MA_TYP;
                if (op->size != 0 && op->size != 1) match |= MA_SIZ;
                break;
            case MR2:                      // 2-byte memory/register in ModRM byte
                if (op->type != MRG && op->type != REG) match |= MA_TYP;
                if (op->size != 0 && op->size != 2) match |= MA_SIZ;
                break;
            case MR4:                      // 4-byte memory/register in ModRM byte
                if (op->type != MRG && op->type != REG) match |= MA_TYP;
                if (op->size != 0 && op->size != 4) match |= MA_SIZ;
                break;
            case RR4:                      // 4-byte memory/register (register only)
                if (op->type != REG) match |= MA_TYP;
                if (op->size != 0 && op->size != 4) match |= MA_SIZ;
                break;
            case MRJ:                      // Memory/reg in ModRM as JUMP target
                if (op->type != MRG && op->type != REG) match |= MA_TYP;
                if (op->size != 0 && op->size != 4) match |= MA_SIZ;
                if ((jmpmode & 0x09) != 0) match |= MA_JMP;
                jmpmode &= 0x7F; break;
            case MR8:                      // 8-byte memory/MMX register in ModRM
            case MRD:                      // 8-byte memory/3DNow! register in ModRM
                if (op->type != MRG && op->type != RMX) match |= MA_TYP;
                if (op->size != 0 && op->size != 8) match |= MA_SIZ;
                break;
            case RR8:                      // 8-byte MMX register only in ModRM
            case RRD:                      // 8-byte memory/3DNow! (register only)
                if (op->type != RMX) match |= MA_TYP;
                if (op->size != 0 && op->size != 8) match |= MA_SIZ;
                break;
            case MMA:                      // Memory address in ModRM byte for LEA
                if (op->type != MRG) match |= MA_TYP; break;
            case MML:                      // Memory in ModRM byte (for LES)
                if (op->type != MRG) match |= MA_TYP;
                if (op->size != 0 && op->size != 6) match |= MA_SIZ;
                if (datasize == 0) datasize = 4; else if (datasize != 4) match |= MA_DIF;
                break;
            case MMS:                      // Memory in ModRM byte (as SEG:OFFS)
                if (op->type != MRG) match |= MA_TYP;
                if (op->size != 0 && op->size != 6) match |= MA_SIZ;
                if ((jmpmode & 0x07) != 0) match |= MA_JMP;
                jmpmode &= 0x7F; break;
            case MM6:                      // Memory in ModRm (6-byte descriptor)
                if (op->type != MRG) match |= MA_TYP;
                if (op->size != 0 && op->size != 6) match |= MA_SIZ;
                break;
            case MMB:                      // Two adjacent memory locations (BOUND)
                if (op->type != MRG) match |= MA_TYP;
                k = op->size; if (ideal == 0 && k > 1) k /= 2;
                if (k != 0 && k != datasize) match |= MA_DIF;
                break;
            case MD2:                      // Memory in ModRM byte (16-bit integer)
            case MB2:                      // Memory in ModRM byte (16-bit binary)
                if (op->type != MRG) match |= MA_TYP;
                if (op->size != 0 && op->size != 2) match |= MA_SIZ;
                break;
            case MD4:                      // Memory in ModRM byte (32-bit integer)
            case MF4:                      // Memory in ModRM byte (32-bit float)
                if (op->type != MRG) match |= MA_TYP;
                if (op->size != 0 && op->size != 4) match |= MA_SIZ;
                break;
            case MD8:                      // Memory in ModRM byte (64-bit integer)
            case MF8:                      // Memory in ModRM byte (64-bit float)
                if (op->type != MRG) match |= MA_TYP;
                if (op->size != 0 && op->size != 8) match |= MA_SIZ;
                break;
            case MDA:                      // Memory in ModRM byte (80-bit BCD)
            case MFA:                      // Memory in ModRM byte (80-bit float)
                if (op->type != MRG) match |= MA_TYP;
                if (op->size != 0 && op->size != 10) match |= MA_SIZ;
                break;
            case MFE:                      // Memory in ModRM byte (FPU environment)
            case MFS:                      // Memory in ModRM byte (FPU state)
            case MFX:                      // Memory in ModRM byte (ext. FPU state)
                if (op->type != MRG) match |= MA_TYP;
                if (op->size != 0) match |= MA_SIZ;
                break;
            case MSO:                      // Source in string operands ([ESI])
                if (op->type != MRG || op->base != REG_ESI ||
                    op->index != -1 || op->offset != 0 || op->anyoffset != 0) match |= MA_TYP;
                if (datasize == 0) datasize = op->size;
                if (op->size != 0 && op->size != datasize) match |= MA_DIF;
                break;
            case MDE:                      // Destination in string operands ([EDI])
                if (op->type != MRG || op->base != REG_EDI ||
                    op->index != -1 || op->offset != 0 || op->anyoffset != 0) match |= MA_TYP;
                if (op->segment != SEG_UNDEF && op->segment != SEG_ES) match |= MA_SEG;
                if (datasize == 0) datasize = op->size;
                if (op->size != 0 && op->size != datasize) match |= MA_DIF;
                break;
            case MXL:                      // XLAT operand ([EBX+AL])
                if (op->type != MXL) match |= MA_TYP; break;
            case IMM:                      // Immediate data (8 or 16/32)
            case IMU:                      // Immediate unsigned data (8 or 16/32)
                if (op->type != IMM) match |= MA_TYP;
                break;
            case VXD:                      // VxD service (32-bit only)
                if (op->type != IMM) match |= MA_TYP;
                if (datasize == 0) datasize = 4;
                if (datasize != 4) match |= MA_SIZ;
                break;
            case JMF:                      // Immediate absolute far jump/call addr
                if (op->type != JMF) match |= MA_TYP;
                if ((jmpmode & 0x05) != 0) match |= MA_JMP;
                jmpmode &= 0x7F; break;
            case JOB:                      // Immediate byte offset (for jumps)
                if (op->type != IMM || longjump) match |= MA_TYP;
                if ((jmpmode & 0x0A) != 0) match |= MA_JMP;
                jmpmode &= 0x7F; break;
            case JOW:                      // Immediate full offset (for jumps)
                if (op->type != IMM) match |= MA_TYP;
                if ((jmpmode & 0x09) != 0) match |= MA_JMP;
                jmpmode &= 0x7F; break;
            case IMA:                      // Immediate absolute near data address
                if (op->type != MRG || op->base >= 0 || op->index >= 0) match |= MA_TYP;
                break;
            case IMX:                      // Immediate sign-extendable byte
                if (op->type != IMM) match |= MA_TYP;
                if (op->offset < -128 || op->offset>127) match |= MA_RNG;
                break;
            case C01:                      // Implicit constant 1 (for shifts)
                if (op->type != IMM || (op->offset != 1 && op->anyoffset == 0))
                    match |= MA_TYP;
                break;
            case IMS:                      // Immediate byte (for shifts)
            case IM1:                      // Immediate byte
                if (op->type != IMM) match |= MA_TYP;
                if (op->offset < -128 || op->offset>255) match |= MA_RNG;
                break;
            case IM2:                      // Immediate word (ENTER/RET)
                if (op->type != IMM) match |= MA_TYP;
                if (op->offset < 0 || op->offset>65535) match |= MA_RNG;
                break;
            case SGM:                      // Segment register in ModRM byte
                if (op->type != SGM) match |= MA_TYP;
                if (datasize == 0) datasize = 2;
                if (datasize != 2) match |= MA_DIF;
                break;
            case SCM:                      // Segment register in command byte
                if (op->type != SGM) match |= MA_TYP;
                break;
            case CRX:                      // Control register CRx
            case DRX:                      // Debug register DRx
                if (op->type != arg) match |= MA_TYP;
                if (datasize == 0) datasize = 4;
                if (datasize != 4) match |= MA_DIF;
                break;
            case PRN:                      // Near return address (pseudooperand)
            case PRF:                      // Far return address (pseudooperand)
            case PAC:                      // Accumulator (AL/AX/EAX, pseudooperand)
            case PAH:                      // AH (in LAHF/SAHF, pseudooperand)
            case PFL:                      // Lower byte of flags (pseudooperand)
            case PS0:                      // Top of FPU stack (pseudooperand)
            case PS1:                      // ST(1) (pseudooperand)
            case PCX:                      // CX/ECX (pseudooperand)
            case PDI:                      // EDI (pseudooperand in MMX extentions)
                break;
            default:                       // Undefined type of operand
                strcpy(errtext, "Internal Assembler error");
                goto error;
            };                               // End of switch (arg)
            if ((jmpmode & 0x80) != 0) match |= MA_JMP;
            if (match != 0) break;             // Some of the operands doesn't match
        };                                 // End of operand matching loop
        if (match == 0) {                    // Exact match found
            if (attempt > 0) {
                --attempt; nameok = 0;
            }         // Well, try to find yet another match
            else break;
        };
    };                                   // End of command search loop
    // Check whether some error was detected. If several errors were found
    // similtaneously, report one (roughly in order of significance).
    if (nameok == 0) {                     // Mnemonic unavailable
        strcpy(errtext, "Unrecognized command");
        asmcmd = nameend; goto error;
    };
    if (match != 0) {                      // Command not found
        if (minop > 0 && aop[minop - 1].type == NNN)
            strcpy(errtext, "Too few operands");
        else if (maxop < 3 && aop[maxop].type != NNN)
            strcpy(errtext, "Too many operands");
        else if (nameok > 1)                 // More that 1 command
            strcpy(errtext, "Command does not support given operands");
        else if (match & MA_JMP)
            strcpy(errtext, "Invalid jump size modifier");
        else if (match & MA_NOP)
            strcpy(errtext, "Wrong number of operands");
        else if (match & MA_TYP)
            strcpy(errtext, "Command does not support given operands");
        else if (match & MA_NOS)
            strcpy(errtext, "Please specify operand size");
        else if (match & MA_SIZ)
            strcpy(errtext, "Bad operand size");
        else if (match & MA_DIF)
            strcpy(errtext, "Different size of operands");
        else if (match & MA_SEG)
            strcpy(errtext, "Invalid segment register");
        else if (match & MA_RNG)
            strcpy(errtext, "Constant out of expected range");
        else
            strcpy(errtext, "Erroneous command");
        goto error;
    };
    // Exact match found. Now construct the code.
    hasrm = 0;                             // Whether command has ModR/M byte
    hassib = 0;                            // Whether command has SIB byte
    dispsize = 0;                          // Size of displacement (if any)
    immsize = 0;                           // Size of immediate data (if any)
    segment = SEG_UNDEF;                   // Necessary segment prefix
    jmpsize = 0;                           // No relative jumps
    memset(tcode, 0, sizeof(tcode));
    *(ulong*)tcode = pd->code & pd->mask;
    memset(tmask, 0, sizeof(tmask));
    *(ulong*)tmask = pd->mask;
    i = pd->len - 1;                         // Last byte of command itself
    if (rep) i++;                        // REPxx prefixes count as extra byte
    // In some cases at least one operand must have explicit size declaration (as
    // in MOV [EAX],1). This preliminary check does not include all cases.
    if (pd->bits == WW || pd->bits == WS || pd->bits == WP) {
        if (datasize == 0) {
            strcpy(errtext, "Please specify operand size"); goto error;
        }
        else if (datasize > 1)
            tcode[i] |= 0x01;                  // WORD or DWORD size of operands
        tmask[i] |= 0x01;
    }
    else if (pd->bits == W3) {
        if (datasize == 0) {
            strcpy(errtext, "Please specify operand size"); goto error;
        }
        else if (datasize > 1)
            tcode[i] |= 0x08;                  // WORD or DWORD size of operands
        tmask[i] |= 0x08;
    };
    // Present suffix of 3DNow! command as immediate byte operand.
    if ((pd->type & C_TYPEMASK) == C_NOW) {
        immsize = 1;
        immediate = (pd->code >> 16) & 0xFF;
    };
    // Process operands again, this time constructing the code.
    anydisp = anyimm = anyjmp = 0;
    for (j = 0; j < 3; j++) {                // Up to 3 operands
        op = aop + j;
        if (j == 0) arg = pd->arg1;
        else if (j == 1) arg = pd->arg2;
        else arg = pd->arg3;
        if (arg == NNN) break;               // All operands processed
        switch (arg) {
        case REG:                        // Integer register in Reg field
        case RG4:                        // Integer 4-byte register in Reg field
        case RMX:                        // MMX register MMx
        case R3D:                        // 3DNow! register MMx
        case CRX:                        // Control register CRx
        case DRX:                        // Debug register DRx
            hasrm = 1;
            if (op->index < 8) {
                tcode[i + 1] |= (char)(op->index << 3); tmask[i + 1] |= 0x38;
            };
            break;
        case RCM:                        // Integer register in command byte
        case RST:                        // FPU register (ST(i)) in command byte
            if (op->index < 8) {
                tcode[i] |= (char)op->index; tmask[i] |= 0x07;
            };
            break;
        case RAC:                        // Accumulator (AL/AX/EAX, implicit)
        case RAX:                        // AX (2-byte, implicit)
        case RDX:                        // DX (16-bit implicit port address)
        case RCL:                        // Implicit CL register (for shifts)
        case RS0:                        // Top of FPU stack (ST(0))
        case MDE:                        // Destination in string op's ([EDI])
        case C01:                        // Implicit constant 1 (for shifts)
            break;                         // Simply skip implicit operands
        case MSO:                        // Source in string op's ([ESI])
        case MXL:                        // XLAT operand ([EBX+AL])
            if (op->segment != SEG_UNDEF && op->segment != SEG_DS)
                segment = op->segment;
            break;
        case MRG:                        // Memory/register in ModRM byte
        case MRJ:                        // Memory/reg in ModRM as JUMP target
        case MR1:                        // 1-byte memory/register in ModRM byte
        case MR2:                        // 2-byte memory/register in ModRM byte
        case MR4:                        // 4-byte memory/register in ModRM byte
        case RR4:                        // 4-byte memory/register (register only)
        case MR8:                        // 8-byte memory/MMX register in ModRM
        case RR8:                        // 8-byte MMX register only in ModRM
        case MRD:                        // 8-byte memory/3DNow! register in ModRM
        case RRD:                        // 8-byte memory/3DNow! (register only)
            hasrm = 1;
            if (op->type != MRG) {           // Register in ModRM byte
                tcode[i + 1] |= 0xC0; tmask[i + 1] |= 0xC0;
                if (op->index < 8) {
                    tcode[i + 1] |= (char)op->index; tmask[i + 1] |= 0x07;
                };
                break;
            };                             // Note: NO BREAK, continue with address
        case MMA:                        // Memory address in ModRM byte for LEA
        case MML:                        // Memory in ModRM byte (for LES)
        case MMS:                        // Memory in ModRM byte (as SEG:OFFS)
        case MM6:                        // Memory in ModRm (6-byte descriptor)
        case MMB:                        // Two adjacent memory locations (BOUND)
        case MD2:                        // Memory in ModRM byte (16-bit integer)
        case MB2:                        // Memory in ModRM byte (16-bit binary)
        case MD4:                        // Memory in ModRM byte (32-bit integer)
        case MD8:                        // Memory in ModRM byte (64-bit integer)
        case MDA:                        // Memory in ModRM byte (80-bit BCD)
        case MF4:                        // Memory in ModRM byte (32-bit float)
        case MF8:                        // Memory in ModRM byte (64-bit float)
        case MFA:                        // Memory in ModRM byte (80-bit float)
        case MFE:                        // Memory in ModRM byte (FPU environment)
        case MFS:                        // Memory in ModRM byte (FPU state)
        case MFX:                        // Memory in ModRM byte (ext. FPU state)
            hasrm = 1; displacement = op->offset; anydisp = op->anyoffset;
            if (op->base < 0 && op->index < 0) {
                dispsize = 4;                  // Special case of immediate address
                if (op->segment != SEG_UNDEF && op->segment != SEG_DS)
                    segment = op->segment;
                tcode[i + 1] |= 0x05;
                tmask[i + 1] |= 0xC7;
            }
            else if (op->index < 0 && op->base != REG_ESP) {
                tmask[i + 1] |= 0xC0;            // SIB byte unnecessary
                if (op->offset == 0 && op->anyoffset == 0 && op->base != REG_EBP)
                    ;                          // [EBP] always requires offset
                else if ((constsize & 1) != 0 &&
                    ((op->offset >= -128 && op->offset < 128) || op->anyoffset != 0)
                    ) {
                    tcode[i + 1] |= 0x40;          // Disp8
                    dispsize = 1;
                }
                else {
                    tcode[i + 1] |= 0x80;          // Disp32
                    dispsize = 4;
                };
                if (op->base < 8) {
                    if (op->segment != SEG_UNDEF && op->segment != addr32[op->base].defseg)
                        segment = op->segment;
                    tcode[i + 1] |=
                        (char)op->base;          // Note that case [ESP] has base<0.
                    tmask[i + 1] |= 0x07;
                }
                else segment = op->segment;
            }
            else {                         // SIB byte necessary
                hassib = 1;
                if (op->base == REG_EBP &&     // EBP as base requires offset, optimize
                    op->index >= 0 && op->scale == 1 && op->offset == 0 && op->anyoffset == 0) {
                    op->base = op->index; op->index = REG_EBP;
                };
                if (op->index == REG_ESP &&    // ESP cannot be an index, reorder
                    op->scale <= 1) {
                    op->index = op->base; op->base = REG_ESP; op->scale = 1;
                };
                if (op->base < 0 &&            // No base means 4-byte offset, optimize
                    op->index >= 0 && op->scale == 2 &&
                    op->offset >= -128 && op->offset < 128 && op->anyoffset == 0) {
                    op->base = op->index; op->scale = 1;
                };
                if (op->index == REG_ESP) {    // Reordering was unsuccessfull
                    strcpy(errtext, "Invalid indexing mode");
                    goto error;
                };
                if (op->base < 0) {
                    tcode[i + 1] |= 0x04;
                    dispsize = 4;
                }
                else if (op->offset == 0 && op->anyoffset == 0 && op->base != REG_EBP)
                    tcode[i + 1] |= 0x04;          // No displacement
                else if ((constsize & 1) != 0 &&
                    ((op->offset >= -128 && op->offset < 128) || op->anyoffset != 0)
                    ) {
                    tcode[i + 1] |= 0x44;          // Disp8
                    dispsize = 1;
                }
                else {
                    tcode[i + 1] |= 0x84;          // Disp32
                    dispsize = 4;
                };
                tmask[i + 1] |= 0xC7;            // ModRM completed, proceed with SIB
                if (op->scale == 2) tcode[i + 2] |= 0x40;
                else if (op->scale == 4) tcode[i + 2] |= 0x80;
                else if (op->scale == 8) tcode[i + 2] |= 0xC0;
                tmask[i + 2] |= 0xC0;
                if (op->index < 8) {
                    if (op->index < 0) op->index = 0x04;
                    tcode[i + 2] |= (char)(op->index << 3);
                    tmask[i + 2] |= 0x38;
                };
                if (op->base < 8) {
                    if (op->base < 0) op->base = 0x05;
                    if (op->segment != SEG_UNDEF && op->segment != addr32[op->base].defseg)
                        segment = op->segment;
                    tcode[i + 2] |= (char)op->base;
                    tmask[i + 2] |= 0x07;
                }
                else segment = op->segment;
            };
            break;
        case IMM:                        // Immediate data (8 or 16/32)
        case IMU:                        // Immediate unsigned data (8 or 16/32)
        case VXD:                        // VxD service (32-bit only)
            if (datasize == 0 && pd->arg2 == NNN && (pd->bits == SS || pd->bits == WS))
                datasize = 4;
            if (datasize == 0) {
                strcpy(errtext, "Please specify operand size");
                goto error;
            };
            immediate = op->offset; anyimm = op->anyoffset;
            if (pd->bits == SS || pd->bits == WS) {
                if (datasize > 1 && (constsize & 2) != 0 &&
                    ((immediate >= -128 && immediate < 128) || op->anyoffset != 0)) {
                    immsize = 1; tcode[i] |= 0x02;
                }
                else immsize = datasize;
                tmask[i] |= 0x02;
            }
            else immsize = datasize;
            break;
        case IMX:                        // Immediate sign-extendable byte
        case IMS:                        // Immediate byte (for shifts)
        case IM1:                        // Immediate byte
            if (immsize == 2)                // To accomodate ENTER instruction
                immediate = (immediate & 0xFFFF) | (op->offset << 16);
            else immediate = op->offset;
            anyimm |= op->anyoffset;
            immsize++; break;
        case IM2:                        // Immediate word (ENTER/RET)
            immediate = op->offset; anyimm = op->anyoffset;
            immsize = 2; break;
        case IMA:                        // Immediate absolute near data address
            if (op->segment != SEG_UNDEF && op->segment != SEG_DS)
                segment = op->segment;
            displacement = op->offset; anydisp = op->anyoffset;
            dispsize = 4; break;
        case JOB:                        // Immediate byte offset (for jumps)
            jmpoffset = op->offset; anyjmp = op->anyoffset;
            jmpsize = 1; break;
        case JOW:                        // Immediate full offset (for jumps)
            jmpoffset = op->offset; anyjmp = op->anyoffset;
            jmpsize = 4; break;
        case JMF:                        // Immediate absolute far jump/call addr
            displacement = op->offset; anydisp = op->anyoffset; dispsize = 4;
            immediate = op->segment; anyimm = op->anyoffset; immsize = 2;
            break;
        case SGM:                        // Segment register in ModRM byte
            hasrm = 1;
            if (op->index < 6) {
                tcode[i + 1] |= (char)(op->index << 3); tmask[i + 1] |= 0x38;
            };
            break;
        case SCM:                        // Segment register in command byte
            if (op->index == SEG_FS || op->index == SEG_GS) {
                tcode[0] = 0x0F; tmask[0] = 0xFF;
                i = 1;
                if (strcmp(name, "PUSH") == 0)
                    tcode[i] = (char)((op->index << 3) | 0x80);
                else
                    tcode[i] = (char)((op->index << 3) | 0x81);
                tmask[i] = 0xFF;
            }
            else if (op->index < 6) {
                if (op->index == SEG_CS && strcmp(name, "POP") == 0) {
                    strcpy(errtext, "Unable to POP CS");
                    goto error;
                };
                tcode[i] = (char)((tcode[i] & 0xC7) | (op->index << 3));
            }
            else {
                tcode[i] &= 0xC7;
                tmask[i] &= 0xC7;
            };
            break;
        case PRN:                        // Near return address (pseudooperand)
        case PRF:                        // Far return address (pseudooperand)
        case PAC:                        // Accumulator (AL/AX/EAX, pseudooperand)
        case PAH:                        // AH (in LAHF/SAHF, pseudooperand)
        case PFL:                        // Lower byte of flags (pseudooperand)
        case PS0:                        // Top of FPU stack (pseudooperand)
        case PS1:                        // ST(1) (pseudooperand)
        case PCX:                        // CX/ECX (pseudooperand)
        case PDI:                        // EDI (pseudooperand in MMX extentions)
            break;                         // Simply skip preudooperands
        default:                         // Undefined type of operand
            strcpy(errtext, "Internal Assembler error");
            goto error;
        };
    };
    // Gather parts of command together in the complete command.
    j = 0;
    if (lock != 0) {                       // Lock prefix specified
        model->code[j] = 0xF0;
        model->mask[j] = 0xFF; j++;
    };
    if (datasize == 2 && pd->bits != FF) {   // Data size prefix necessary
        model->code[j] = 0x66;
        model->mask[j] = 0xFF; j++;
    };
    if (addrsize == 2) {                   // Address size prefix necessary
        model->code[j] = 0x67;
        model->mask[j] = 0xFF; j++;
    };
    if (segment != SEG_UNDEF) {            // Segment prefix necessary
        if (segment == SEG_ES) model->code[j] = 0x26;
        else if (segment == SEG_CS) model->code[j] = 0x2E;
        else if (segment == SEG_SS) model->code[j] = 0x36;
        else if (segment == SEG_DS) model->code[j] = 0x3E;
        else if (segment == SEG_FS) model->code[j] = 0x64;
        else if (segment == SEG_GS) model->code[j] = 0x65;
        else { strcpy(errtext, "Internal Assembler error"); goto error; };
        model->mask[j] = 0xFF; j++;
    };
    if (dispsize > 0) {
        memcpy(tcode + i + 1 + hasrm + hassib, &displacement, dispsize);
        if (anydisp == 0) memset(tmask + i + 1 + hasrm + hassib, 0xFF, dispsize);
    };
    if (immsize > 0) {
        if (immsize == 1) l = 0xFFFFFF00L;
        else if (immsize == 2) l = 0xFFFF0000L;
        else l = 0L;
        if ((immediate & l) != 0 && (immediate & l) != l) {
            strcpy(errtext, "Constant does not fit into operand");
            goto error;
        };
        memcpy(tcode + i + 1 + hasrm + hassib + dispsize, &immediate, immsize);
        if (anyimm == 0) memset(tmask + i + 1 + hasrm + hassib + dispsize, 0xFF, immsize);
    };
    i = i + 1 + hasrm + hassib + dispsize + immsize;
    jmpoffset = jmpoffset - (i + j + jmpsize);
    model->jmpsize = jmpsize;
    model->jmpoffset = jmpoffset;
    model->jmppos = i + j;
    if (jmpsize != 0) {
        if (ip != 0) {
            jmpoffset = jmpoffset - ip;
            if (jmpsize == 1 && anyjmp == 0 && (jmpoffset < -128 || jmpoffset >= 128)) {
                if (longjump == 0 && (jmpmode & 0x03) == 0) {
                    longjump = 1;
                    goto retrylongjump;
                };
                sprintf(errtext,
                    "Relative jump out of range, use %s LONG form", name);
                goto error;
            };
            memcpy(tcode + i, &jmpoffset, jmpsize);
        };
        if (anyjmp == 0) memset(tmask + i, 0xFF, jmpsize);
        i += jmpsize;
    };
    memcpy(model->code + j, tcode, i);
    memcpy(model->mask + j, tmask, i);
    i += j;
    model->length = i;
    return i;                            // Positive value: length of code
error:
    model->length = 0;
    return cmd - asmcmd;                   // Negative value: position of error
};

//#pragma option -O.                     // Restore old optimization options
